﻿//-----------------------------------------------------------------------------------------------------------
// WBFSSync Project by Omega Frost 
// http://wbfssync.codeplex.com/
//
// WBFSSync is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using WBFSe3.Native;
using System.Runtime.InteropServices;
using WBFSe3.Wbfs;

namespace WBFSe3.IO
{
    //-------------------------------------------------------------------------------------------------------
    //
    //-------------------------------------------------------------------------------------------------------
    public delegate void DriveUpdatedDelegate(WbfsDriveInfo sender, WbfsError error);
    public delegate void DriveClosedDelegate(WbfsDriveInfo sender);


    //-------------------------------------------------------------------------------------------------------
    //
    //-------------------------------------------------------------------------------------------------------
    public class WbfsDriveInfo : FileSystemInfo
    {
        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        private static HashSet<uint> FatWbfsDrives = new HashSet<uint>();
        private static WbfsDriveInfo[] Drives = new WbfsDriveInfo[26];


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected String name;
        protected String label;
        protected uint serial;
        protected bool largeFiles;

        protected long size;
        protected long used;
        protected long free;

        protected bool isReadOnly;
        protected bool isSparse;
        protected bool isFixed;
        protected bool isCd;
        protected bool isWbfs;
        protected bool isFatWbfs;

        protected bool isReady;
        protected bool isGame;

        protected int error;


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public event DriveUpdatedDelegate DriveUpdated;
        public event DriveClosedDelegate DriveClosed;


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override String Name { get { return this.name; } }
        public override string FullName { get { return this.name; } }

        public String Label { get { return this.label; } set { SetLabel(value); } }
        public uint Serial { get { return this.serial; } }
        public Boolean LargeFiles { get { return this.largeFiles; } }

        public long Size { get { lock (this) { ParseSpace(); } return this.size; } }
        public long Used { get { lock (this) { ParseSpace(); } return this.used; } }
        public long Free { get { lock (this) { ParseSpace(); } return this.free; } }

        public Boolean IsReadOnly { get { return this.isReadOnly; } }
        public Boolean IsFatWbfs { get { return FatWbfsDrives.Contains(this.serial); } set { SetFatWbfs(value); } }
        public Boolean IsSparse { get { return this.isSparse; } }
        public Boolean IsFixed { get { return this.isFixed; } }
        public Boolean IsWbfs { get { return this.isWbfs; } }
        public Boolean IsCd { get { return this.isCd; } }

        public Boolean IsReady { get { return this.isReady; } }
        public Boolean IsGame { get { return this.isGame; } }
        public override bool Exists { get { return true; } }

        public Boolean IsValid { get { return this.error != 0; } }
        public int Error { get { return this.error; } }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static void AddFatWbfsSN(uint sn)
        {
            if (!FatWbfsDrives.Contains(sn))
                FatWbfsDrives.Add(sn);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static uint[] GetFatWbfsSNs()
        {
            return FatWbfsDrives.ToArray();
        }

        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public static WbfsError GetDrive(String path, out WbfsDriveInfo drive)
        {
            WbfsDriveInfo di;
            char letter;
            int error;
            drive = null;

            // Path verification
            if (String.IsNullOrEmpty(path))
                return WbfsError.DriveNotFound;

            // Get the drive letter
            letter = Path.GetFullPath(path).ToUpper()[0];

            // Return already created file
            di = Drives[(int)(letter - 'A')];
            if (di != null)
            {
                if ((error = (int)di.Update()) != 0)
                {
                    drive = di;
                    Drives[(int)(letter - 'A')] = null;
                    return (WbfsError)error;
                }

                drive = di;
                return WbfsError.Ok;
            }

            // Create new instance of a drive info
            di = new WbfsDriveInfo();
            di.name = letter + ":\\";

            // Is drive type supported?
            if (di.ParseType() != 0)
                return WbfsError.DriveNotSupported;

            // Check for a known filesystem
            if ((error = di.ParseVolume()) == 0)
            {
                //Calculate space
                if ((error = di.ParseSpace()) != 0)
                    return (WbfsError)error;
            }
            else if (!di.isCd) //CD Drives just sucks...
            {
                if (error == 1005)
                {
                    //Maybe wbfs drive or game
                    if (di.isCd)
                    {
                        if (di.isReady)
                        {
                            //Try build the DVD as a game
                        }
                    }
                    else
                    {
                        //Try open a wbfs device
                        WbfsDevice wdi;
                        error = (int)WbfsDevice.Open(di.name,
                            FileAccess.ReadWrite, false, out wdi);

                        if (error == 0)
                        {
                            di = wdi;
                        }
                    }
                }
                else if (error != 21)
                {
                    return (WbfsError)error;
                }
            }

            Drives[(int)(letter - 'A')] = di;
            drive = di;
            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        internal static void InvalidateDrive(Char drive)
        {
            int old = (int)(Char.ToUpper(drive) - 'A');
            Drives[old].OnDriveClosed();
            Drives[old] = null;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        internal static void UpdateDrive(WbfsDriveInfo drive)
        {
            int old = (int)(Char.ToUpper(drive.name[0]) - 'A');

            drive.DriveClosed = Drives[old].DriveClosed;
            drive.DriveUpdated = Drives[old].DriveUpdated;
            Drives[old] = drive;

            drive.OnDriveUpdated(WbfsError.DriveReplace);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected int ParseType()
        {
            // Get the drive type
            uint type = WbfsNative.GetDriveType(this.name);

            switch (type)
            {
                case 2: //Removable
                    this.isFixed = false;
                    this.isCd = false;
                    break;

                case 3: //Fixed
                    this.isFixed = true;
                    this.isCd = false;
                    break;

                case 5: //CD
                    this.isFixed = true;
                    this.isCd = true;
                    break;

                default: //Not Supported
                    return -1;
            }

            return 0;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        private int ParseVolume()
        {
            // Get the volume information

            EFileSystemFeature volFeatures;
            StringBuilder volName = new StringBuilder(261);
            StringBuilder volFs = new StringBuilder(261);

            uint volSn;
            uint volMaxFile;

            this.isReady = WbfsNative.GetVolumeInformation(this.name, volName, 
                261, out volSn, out volMaxFile, out volFeatures, volFs, 261);

            if (this.isReady)
            {
                this.serial = volSn;
                this.label = volName.ToString();
                this.isSparse = volFeatures.HasFlag(EFileSystemFeature.SupportsSparseFiles);
                this.isReadOnly = volFeatures.HasFlag(EFileSystemFeature.ReadOnlyVolume);

                if ((volName.ToString() == "FAT32") || (volName.ToString() == "FAT"))
                    this.largeFiles = false;
                else this.largeFiles = true;

                return 0;
            }
            else
            {
                return Marshal.GetLastWin32Error();
            }
            
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected virtual int ParseSpace()
        {
            if (!this.isReady) 
                return 0;

            ulong volFree, volTotal, volDummy;
            if (!WbfsNative.GetDiskFreeSpaceEx(this.name,
                    out volFree, out volTotal, out volDummy))
            {
                return Marshal.GetLastWin32Error();
            }

            this.size = (long)volTotal;
            this.free = (long)volFree;
            this.used = (long)(volTotal - volFree);

            return 0;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected WbfsDriveInfo()
        {
            this.name = null;
            this.label = null;
            this.serial = 0;

            this.size = 0;
            this.used = 0;
            this.free = 0;

            this.isReadOnly = false;
            this.isSparse = false;
            this.isFixed = false;
            this.isCd = false;
            this.isWbfs = false;
            this.isFatWbfs = false;

            this.isReady = false;
            this.isGame = false;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected virtual void SetLabel(String value)
        {
            if (!WbfsNative.SetVolumeLabel(this.name, value))
            {
                int error = Marshal.GetLastWin32Error();
                switch (error)
                {
                    default: throw new WbfsIOException(error);
                    case 5: throw new UnauthorizedAccessException();
                }
            }
            else
            {
                this.label = value;
            }
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected void SetFatWbfs(Boolean value)
        {
            if (this.isWbfs || this.isCd || !this.isReady)
                return;

            lock (FatWbfsDrives)
            {
                if (value) FatWbfsDrives.Add(this.serial);
                else FatWbfsDrives.Remove(this.serial);
            }
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public virtual WbfsError Update()
        {
            int error;
            lock (this)
            {
                if ((error = ParseVolume()) == 0)
                    error = ParseSpace();
            }

            OnDriveUpdated((WbfsError)error);

            if (error != 0)
            {
                if (this.isCd || (error == 21) || (error == 1005))
                {
                    this.isReady = false;
                    return WbfsError.Ok;
                }
                else
                {
                    OnDriveClosed();
                    Drives[(int)(this.name[0] - 'A')] = null;
                }
            }
            else
            {
                this.isReady = true;
            }

            return (WbfsError)error;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public new void Refresh()
        {
            Update();
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public virtual WbfsError Close()
        {
            return WbfsError.Ok;
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        public override void Delete()
        {
            // Nops
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected void OnDriveUpdated(WbfsError error)
        {
            if (this.DriveUpdated != null)
                this.DriveUpdated(this, error);
        }


        //---------------------------------------------------------------------------------------------------
        //
        //---------------------------------------------------------------------------------------------------
        protected void OnDriveClosed()
        {
            if (this.DriveClosed != null)
                this.DriveClosed(this);
        }
    }
}
